#include <ctype.h>

#include "assembler.h"

#define MAX_STRING_LENGTH 256

struct token {
	enum {
		TOK_DIRECTIVE,		// .foo
		TOK_LABEL,			// foo:
		TOK_MNEMONIC,		// *a<a,f.!c
		TOK_REGISTER,		// rN, @, .named
		TOK_STRING,			// "foo"
		TOK_NUMBER,			// 89123, 0xF00
		TOK_SYMBOL,			// anything else
		TOK_OPEN_PAREN,		// (
		TOK_CLOSE_PAREN,	// )
		TOK_OPERATOR,		// +, -
		TOK_ENDLINE,		// \n
		TOK_EOF,			// EOF
		TOK_UNKNOWN
	} type;

	union {
		unsigned number;	//set by TOK_NUMBER
		char *string;		//set by TOK_STRING; malloc'ed
	};
};

int c_to_i(int c) {
	switch(c) {
		case '0' ... '9': return c - '0';
		case 'a' ... 'z': return 10 + c - 'a';
		case 'A' ... 'Z': return 10 + c - 'A';
		default: return -1;
	}
}

struct token read_number(file_t f, int base) {
	struct token tok = { type: TOK_NUMBER };

	int c = getc(f->file);
	tok.number = c_to_i(c);
	if(tok.number == -1) error(f, "invalid number");
	
	while(-1 != c_to_i(c = getc(f->file))) {
		tok.number *= base;
		tok.number += c_to_i(c);
	}

	ungetc(c, f->file);
	return tok;
}

struct token read_string(file_t f) {
	struct token tok = { type: TOK_STRING };
	char *string = tok.string = malloc(MAX_STRING_LENGTH);

	static int escape[256] = {
		['a'] = '\a' - 'a',
		['b'] = '\b' - 'b',
		['f'] = '\f' - 'f',
		['n'] = '\n' - 'n',
		['r'] = '\r' - 'r',
		['t'] = '\t' - 't',
	};

	for(;;) {
		int c = getc(f->file);
		if(c == '"') {
			string = '\0';
			break;
		} else if(c == '\\') {
			c  = getc(f->file);
			*string++ = (char) (c + escape[(unsigned char) c]);
		} else {
			*string++ = (char) c;
		}
	}

	return tok;
}

struct token read_token(file_t f) {
	int c = getc(f->file);

	struct token tok = { type: TOK_UNKNOWN };
	switch(c) {

	/* whitespace cases */

	case ' ':
	case '\t': //skip leading whitespace
		tok = read_token(f);
		break;

	case '#': //skip comment; flow to '\n'
		do c = getc(f->file);
		while(c != '\n');
	case '\n': //report an end of line
		++f->line;
		tok.type = TOK_ENDLINE;	
		break;	

	/* trivial cases */

	case EOF: tok.type = TOK_EOF;			break;
	case '(': tok.type = TOK_OPEN_PAREN;	break;
	case ')': tok.type = TOK_CLOSE_PAREN;	break;

	/* numbers */	

	case '0':
		c = getc(f->file);
		if(c == 'x' || c == 'X') {
			tok = read_number(f, 16);
			break;
		}
		/* flowthrough */
	case '1' ... '9':
		ungetc(c, f->file);
		tok = read_number(f, 10);
		break;

	/* strings */

	case '"':
		tok = read_string(f);
		break;

	/* operators */
	
	case '~':	//unary inversion
	case '-':	//unary negation, binary subtraction
	case '+':	//addition
	case '*':	//multiplication
	case '<':	//less than [or <<, <=]
	case '>':	//greater than [or >>, >=]
	case '|':	//bitwise or [or ||]
	case '&':	//bitwise and [or &&]
	case '^':	//bitwise xor
	case '!':	//nothing [or !=]
	case '=':	//nothing [or ==]
	case '/':	//division
	case '%':	//modulus

	/* remaining */
	//	TOK_DIRECTIVE,		// .foo
	//	TOK_LABEL,			// foo:
	//	TOK_MNEMONIC,		// *a<a,f.!c
	//	TOK_REGISTER,		// rN, @, .named
	//	TOK_SYMBOL,			// anything else
	//	TOK_OPERATOR,		// +, -
	//	- is a tricky operator; binary and unary
	//	* is a tricky operator; mnemonics can start with it; can fix
	//	one way to handle all of these is to handle operator explicitly,
	//	then have all the others snarf an entire symbol up to the next
	//	space or #, then look at what was snarfed to tag the result.
	//	this is made easier if we change * to be an operator only.
	//	the problem here is that we don't really want to stop at the
	//	next space, as that would imply x+y is one symbol; but we can't
	//	stop at non-letter characters, because currently
	//		0&a>,f.!c
	//	is one mnemonic... and on a related note, this starts with zero,
	//	so is already mishandled :-(. It's too bad we can't just clean
	//	up mnemonics to avoid all these problems; all the other tokens have
	//	sane character sets.

	default:
		printf("%c", c);
		break;

	}

	return tok;
}

void test(file_t f) {
	for(;;) {
		struct token tok = read_token(f);
		if(tok.type == TOK_ENDLINE || tok.type == TOK_EOF) printf("\n");
		else if(tok.type == TOK_NUMBER) printf("%i", tok.number);
		else if(tok.type == TOK_STRING) printf("%s", tok.string);
		else printf(" ");
		if(tok.type == TOK_EOF) return;
	}
}
